/*******************************************************************************
 * Copyright (C) 2012 Maxim Integrated, All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of Maxim Integrated shall
 * not be used except as stated in the Maxim Integrated Branding Policy.
 *
 * The mere transfer of this software does not imply any licenses
 * of trade secrets, proprietary technology, copyrights, patents,
 * trademarks, maskwork rights, or any other form of intellectual
 * property whatsoever. Maxim Integrated retains all ownership rights.
 *
 *     Description: MAXQ1741 stdio routines
 *             $Id: maxq_stdio.c 5786 2012-11-19 20:14:53Z jeremy.brodt $
 *
 *******************************************************************************
 */

#include <stdarg.h>
#include <stdio.h>
#include "maxq_config.h"
#include "comm\maxq_comm.h"

//*****************************************************************************
// A mapping from an integer between 0 and 15 to its ASCII character
// equivalent.
//*****************************************************************************
static const char * const g_pcHex = "0123456789abcdef";


//*****************************************************************************
int _maxq_write(const char *pcBuf, unsigned int ulLen, int (*locputchar)(int))
{
    unsigned int uIdx;

    // Check for valid arguments.

    if (pcBuf == 0) {
      return 0;
    }

    // Send the characters
    for(uIdx = 0; uIdx < ulLen; uIdx++) {

        // If the character to the JTAG is \n, then add a \r before it so that
        // \n is translated to \n\r in the output.
        if ( (pcBuf[uIdx] == '\n') && (uIdx > 0) && (pcBuf[uIdx - 1] != '\r') ) {
          locputchar('\r');
        }

        // Send the character to the UART output.
        locputchar(pcBuf[uIdx]);
    }

    // Return the number of characters written.
    return(uIdx);
}

//*****************************************************************************
int maxq_write(const char *pcBuf, unsigned int ulLen)
{
  return _maxq_write(pcBuf, ulLen, putchar);
}

//*****************************************************************************
int _maxq_vprintf(const char *pcString, va_list vaArgP, int (*locputchar)(int))
{
    unsigned int ulIdx, ulValue, ulPos, ulCount, ulBase, ulNeg;
    char *pcStr, pcBuf[16], cFill;
    unsigned int sent = 0;

    // Loop while there are more characters in the string.
    while(*pcString) {

        // Find the first non-% character, or the end of the string.
        for(ulIdx = 0; (pcString[ulIdx] != '%') && (pcString[ulIdx] != '\0'); ulIdx++);

        // Write this portion of the string.
        sent += _maxq_write(pcString, ulIdx, locputchar);

        // Skip the portion of the string that was written.
        pcString += ulIdx;

        // See if the next character is a %.
        if(*pcString == '%') {

            // Skip the %.
            pcString++;

            // Set the digit count to zero, and the fill character to space
            // (i.e. to the defaults).
            ulCount = 0;
            cFill = ' ';

            // It may be necessary to get back here to process more characters.
            // Goto's aren't pretty, but effective.  I feel extremely dirty for
            // using not one but two of the beasts.
again:

            // Determine how to handle the next character.
            switch(*pcString++) {

                // Handle the digit characters.
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                {
                    // If this is a zero, and it is the first digit, then the
                    // fill character is a zero instead of a space.
                    if((pcString[-1] == '0') && (ulCount == 0)) {
                        cFill = '0';
                    }

                    // Update the digit count.
                    ulCount *= 10;
                    ulCount += pcString[-1] - '0';

                    // Get the next character.
                    goto again;
                }

                // Handle the %c command.
                case 'c': {

                    // Get the value from the varargs.
                    ulValue = va_arg(vaArgP, unsigned int);

                    // Print out the character.
                    sent += _maxq_write((char *)&ulValue, 1, locputchar);

                    // This command has been handled.
                    break;
                }

                // Handle the %d command.
                case 'd': {

                    // Get the value from the varargs.
                    ulValue = va_arg(vaArgP, unsigned int);

                    // Reset the buffer position.
                    ulPos = 0;

                    // If the value is negative, make it positive and indicate
                    // that a minus sign is needed.
                    if((int)ulValue < 0) {

                        // Make the value positive.
                        ulValue = -(int)ulValue;

                        // Indicate that the value is negative.
                        ulNeg = 1;
                    }

                    else {
                        // Indicate that the value is positive so that a minus
                        // sign isn't inserted.
                        ulNeg = 0;
                    }

                    // Set the base to 10.
                    ulBase = 10;

                    // Convert the value to ASCII.
                    goto convert;
                }

                // Handle the %s command.
                case 's': {

                    // Get the string pointer from the varargs.
                    pcStr = va_arg(vaArgP, char *);

                    // Determine the length of the string.
                    for(ulIdx = 0; pcStr[ulIdx] != '\0'; ulIdx++);

                    // Write the string.
                    sent += _maxq_write(pcStr, ulIdx, locputchar);

                    // Write any required padding spaces
                    if(ulCount > ulIdx) {
                        ulCount -= ulIdx;

                        while(ulCount--) {
                            sent += _maxq_write(" ", 1, locputchar);
                        }
                    }

                    // This command has been handled.
                    break;
                }

                // Handle the %u command.
                case 'u': {
                    // Get the value from the varargs.
                    ulValue = va_arg(vaArgP, unsigned int);

                    // Reset the buffer position.
                    ulPos = 0;

                    // Set the base to 10.
                    ulBase = 10;

                    // Indicate that the value is positive so that a minus sign
                    // isn't inserted.
                    ulNeg = 0;

                    // Convert the value to ASCII.
                    goto convert;
                }

                // Handle the %x and %X commands.  Note that they are treated
                // identically; i.e. %X will use lower case letters for a-f
                // instead of the upper case letters is should use.  We also
                // alias %p to %x.
                case 'x':
                case 'X':
                case 'p':
                {
                    // Get the value from the varargs.
                    ulValue = va_arg(vaArgP, unsigned int);

                    // Reset the buffer position.
                    ulPos = 0;

                    // Set the base to 16.
                    ulBase = 16;

                    // Indicate that the value is positive so that a minus sign
                    // isn't inserted.
                    ulNeg = 0;

                    // Determine the number of digits in the string version of
                    // the value.
convert:
                    for(ulIdx = 1; (((ulIdx * ulBase) <= ulValue) && (((ulIdx * ulBase) / ulBase) == ulIdx)); ulIdx *= ulBase, ulCount--);

                    // If the value is negative, reduce the count of padding
                    // characters needed.
                    if(ulNeg) {
                        ulCount--;
                    }

                    // If the value is negative and the value is padded with
                    // zeros, then place the minus sign before the padding.
                    if(ulNeg && (cFill == '0')) {

                        // Place the minus sign in the output buffer.
                        pcBuf[ulPos++] = '-';

                        // The minus sign has been placed, so turn off the
                        // negative flag.
                        ulNeg = 0;
                    }

                    // Provide additional padding at the beginning of the
                    // string conversion if needed.
                    if((ulCount > 1) && (ulCount < 16)) {
                        for(ulCount--; ulCount; ulCount--) {
                            pcBuf[ulPos++] = cFill;
                        }
                    }

                    // If the value is negative, then place the minus sign
                    // before the number.
                    if(ulNeg) {
                        // Place the minus sign in the output buffer.
                        pcBuf[ulPos++] = '-';
                    }

                    // Convert the value into a string.
                    for(; ulIdx; ulIdx /= ulBase) {
                        pcBuf[ulPos++] = g_pcHex[(ulValue / ulIdx) % ulBase];
                    }

                    // Write the string.
                    sent += _maxq_write(pcBuf, ulPos, locputchar);

                    // This command has been handled.
                    break;
                }

              // Handle the %% command.
              case '%': {
                // Simply write a single %.
                sent += _maxq_write(pcString - 1, 1, locputchar);

                // This command has been handled.
                break;
              }

              // Handle all other commands.
              default: {
                // Indicate an error.
                sent += _maxq_write("ERROR", 5, locputchar);

                // This command has been handled.
                break;
              }
            }
        }
    }

  return sent;
}

//*****************************************************************************
int maxq_vprintf(const char *pcString, va_list vaArgP)
{
  return _maxq_vprintf(pcString, vaArgP, putchar);
}

//*****************************************************************************
void maxq_printf(const char *pcString, ...)
{
  va_list vaArgP;

  // Check the arguments.
  if (pcString == 0)
    return;

  // Start the varargs processing.
  va_start(vaArgP, pcString);

  maxq_vprintf(pcString, vaArgP);

  // End the varargs processing.
  va_end(vaArgP);
}
